package ch.ethz.fcl.metrobuzz.data.interchange;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.algorithm.list.ListOperation;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.roadnetwork.RoadNetwork;
import ch.ethz.fcl.metrobuzz.data.scene.MBScene;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.singapore.SGInterchange;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Polygon;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrofont.MetroFont;

public class Region extends Interchange {
	protected int zoneIndex;
	private Polygon zone;
	private ArrayList<ArrayList<Point2D>> connectedBoundaryPoints;

	public Region(int index) {
		this.index = index;
		arcElementColorMap = new HashMap<Interchange, Integer>();
	}

	public void init(MBData data, SGInterchange interchange, int level) {
		initEdges(data.getRoadNetwork(), interchange);
		initConnectedRegion(data.getScene(), interchange, level);
		initColors(interchange, level);
		initCircosColors();
		initInterchangeMatrix(data.getScene(), interchange,
				interchange.getRegions(level));
	}

	private void initEdges(RoadNetwork rn, SGInterchange sr) {
		incomingEdges = new ArrayList<Edge>();
		outgoingEdges = new ArrayList<Edge>();

		for (Edge edge : rn.getPassingEdges()) {
			if (!outgoingEdges.contains(edge))
				if (getZone().contains(edge.getStartNode().toPoint())
						&& !getZone().contains(edge.getEndNode().toPoint()))
					outgoingEdges.add(edge);
			if (!incomingEdges.contains(edge))
				if (getZone().contains(edge.getEndNode().toPoint())
						&& !getZone().contains(edge.getStartNode().toPoint()))
					incomingEdges.add(edge);
		}
	}

	private void initConnectedRegion(MBScene scene, SGInterchange sr, int level) {
		connectedInterchanges = new ArrayList<Interchange>();
		connectedBoundaryPoints = new ArrayList<ArrayList<Point2D>>();

		List<Region> regions = sr.getRegions(level);
		for (int i = 0; i < regions.size(); i++) {
			if (regions.get(i).index != index) {
				Polygon p = regions.get(i).getZone();
				if (zone.getBound().intersects(p.getBound())) {
					ArrayList<Point2D> points = zone.adjacent(p);
					if (points != null && points.size() > 1) {
						connectedInterchanges.add(regions.get(i));
						connectedBoundaryPoints.add(points);
					}
				}
			}
		}
	}

	private void initColors(SGInterchange sr, int level) {
		if (connectedInterchanges == null)
			return;

		if (level == 0)
			for (Interchange i : connectedInterchanges) {
				Region r = (Region) i;
				if (arcElementColorMap.get(r) != null)
					continue;

				int colorIndex = InterchangeColor.pickColorRange(this, r);

				setColorMap(r, colorIndex);
				r.setColorMap(this, colorIndex);
			}
		else {
			for (Interchange i : connectedInterchanges) {
				Region r = (Region) i;
				if (arcElementColorMap.get(r) != null)
					continue;
				int colorIndex = -1;

				List<Region> parents = sr.getRegions(level - 1);
				Region r1parent = getParent(parents, this), r2parent = getParent(
						parents, r);

				if (r1parent.getIndex() != r2parent.getIndex()) {
					colorIndex = r1parent.getConnectedColorIndex(r2parent);

					colorIndex = InterchangeColor.pickColorInRange(colorIndex,
							this, r);
				} else {
					colorIndex = InterchangeColor.pickColorRange(this, r);
				}
				// MBTools.debug("Parent1 index " + r1parent.getIndex()
				// + " parent2 index " + r2parent.getIndex(), true);
				// MBTools.debug("Set region " + this.getIndex() + " and "
				// + r.index + " color " + colorIndex + "\n", true);
				setColorMap(r, colorIndex);
				r.setColorMap(this, colorIndex);
			}
		}
	}

	private void initCircosColors() {
		if (connectedInterchanges == null || cd == null)
			return;

		for (int i = 0; i < connectedInterchanges.size(); i++)
			cd.getData().setColor(i + 1,
					arcElementColorMap.get(connectedInterchanges.get(i)));
	}

	private void initInterchangeMatrix(MBScene scene, SGInterchange sr,
			List<? extends Interchange> interchanges) {		
		matrixSize = connectedInterchangeIndices.size() + 1;
		linkDirs = new Vector2d[matrixSize - 1];
		linkColors = new int[matrixSize - 1];

		connectedInterchanges = new ArrayList<Interchange>();
		for (int index : connectedInterchangeIndices)
			connectedInterchanges.add(interchanges.get(index));

		for (int i = 0; i < matrixSize - 1; i++) {
			Region cn = (Region) connectedInterchanges.get(i);
			Point2D cnCentroid = cn.circosCentroid;
			linkDirs[i] = new Vector2d(cnCentroid.getX()
					- circosCentroid.getX(), cnCentroid.getY()
					- circosCentroid.getY());
		}

		for (int i = 0; i < connectedInterchangeColorIndices.size(); i++)
			arcElementColorMap.put(connectedInterchanges.get(i),
					connectedInterchangeColorIndices.get(i));

		for (int i = 0; i < matrixSize - 1; i++) {
			Region cn = (Region) connectedInterchanges.get(i);
			linkColors[i] = arcElementColorMap.get(cn);
		}

		connectedBoundaryPoints = new ArrayList<ArrayList<Point2D>>();
		for (Interchange i : connectedInterchanges) {
			Region region = (Region) i;
			ArrayList<Point2D> adjacent = zone.adjacent(region.getZone());
			connectedBoundaryPoints.add(adjacent);
		}
	}

	// return the parent region in coarsest level up
	private Region getParent(List<Region> parents, Region r) {
		Region parent = null;
		for (int i = 0; i < parents.size(); i++) {
			if (parent == null
					&& parents.get(i).getZone().contains(r.circosCentroid))
				parent = parents.get(i);
		}

		if (parent == null)
			parent = closestRegion(parents, r);

		return parent;
	}

	private Region closestRegion(List<Region> regions, Region r) {
		float minDist = Float.MAX_VALUE;
		Region closest = null;
		for (int i = 0; i < regions.size(); i++) {
			float dist = r.circosCentroid
					.distance(regions.get(i).circosCentroid);
			if (minDist > dist) {
				minDist = dist;
				closest = regions.get(i);
			}
		}
		return closest;
	}

	@SuppressWarnings("unchecked")
	public void initIncomingOutgoing() {
		outgoingTripIds = (ArrayList<Integer>[]) new ArrayList<?>[MBData.ONE_DAY_TIME];
		incomingTripIds = (ArrayList<Integer>[]) new ArrayList<?>[MBData.ONE_DAY_TIME];

		for (int i = 0; i < MBData.ONE_DAY_TIME; i++) {
			float start = i / (float) MBData.ONE_DAY_TIME;
			float end = (i + 1) / (float) MBData.ONE_DAY_TIME;
			outgoingTripIds[i] = new ArrayList<Integer>();
			for (Edge edge : outgoingEdges) {
				outgoingTripIds[i] = ListOperation.symUnion(outgoingTripIds[i],
						edge.getPassTripIds(start, end));
			}

			incomingTripIds[i] = new ArrayList<Integer>();
			for (Edge edge : incomingEdges) {
				incomingTripIds[i] = ListOperation.symUnion(incomingTripIds[i],
						edge.getPassTripIds(start, end));
			}
		}
	}

	public List<Integer> getOutgoingTripIds(int start, int end) {
		List<Integer> tripIds = new ArrayList<Integer>();

		for (int i = start; i <= Math.min(end, MBData.ONE_DAY_TIME - 1); i++)
			tripIds = ListOperation.symUnion(tripIds, outgoingTripIds[i]);
		return tripIds;
	}

	public List<Integer> getIncomingTripIds(int start, int end) {
		List<Integer> tripIds = new ArrayList<Integer>();

		for (int i = start; i <= Math.min(end, MBData.ONE_DAY_TIME - 1); i++)
			tripIds = ListOperation.symUnion(tripIds, incomingTripIds[i]);
		return tripIds;
	}

	@Override
	public void render(Rectangle bound, MetroFont fm) {
		if (!zone.intersects(bound))
			return;

		if (cd != null)
			cd.render(fm);
	}

	public void renderAdjacentBoundary(Rectangle bound) {
		if (connectedBoundaryPoints == null || arcElementColorMap == null)
			return;

		GL11.glLineWidth(2.0f);
		for (int i = 0; i < connectedBoundaryPoints.size(); i++) {
			ArrayList<Point2D> points = connectedBoundaryPoints.get(i);
			Integer color = arcElementColorMap.get(connectedInterchanges.get(i));
			if (color == null)
				continue;
			MBColorTable.assignColor(color, 1.0f);
			MBGL.render(points, GL11.GL_LINE_STRIP);

			// ColorTable.assignColor(ColorTable.BLACK, 0.7f);
			// MBGL.render(zone.getPoints(), GL11.GL_LINE_STRIP);
		}
	}

	public void setZoneIndex(MBData data, int zoneIndex, int level) {
		this.zoneIndex = zoneIndex;
		zone = data.getSGInfo().getSGZones().getZone(level, zoneIndex);
		circosCentroid = zone.getCentroid();
	}

	public void addEdge(Edge edge) {
		if (edges == null)
			edges = new ArrayList<Edge>();
		edges.add(edge);
	}

	public int getConnectedColorIndex(Region conencted) {
		if (arcElementColorMap == null)
			return -1;
		if (arcElementColorMap.get(conencted) == null)
			return -1;
		return arcElementColorMap.get(conencted);
	}

	public Polygon getZone() {
		return zone;
	}
}
